package com.seed.launcher.listener.bluetooth;

import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

import com.seed.launcher.listener.Registerable;
import com.seed.launcher.listener.bean.BluetoothInfo;
import com.seed.launcher.listener.util.ThreadUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author xct
 * create on: 2022/9/1 16:01
 */
public class BluetoothScanner implements Registerable {

    private BluetoothManager mBluetoothManager;
    private BluetoothLeScanner mBluetoothLeScanner;
    private BluetoothAdapter mAdapter;
    private Context mContext;
    private Handler mHandler;

    private static final String TAG = "BluetoothScanner";
    private static boolean loggable = false;

    private static final int MSG_SCAN_BLUETOOTH = 10;

    //扫描间隔，上次扫描完成到下次扫描开始
    private static final long DELAY_SCAN_BLUETOOTH = 10 * DateUtils.SECOND_IN_MILLIS;
    //过期时长
    private static final long EXPIRE_DURATION = 30 * DateUtils.SECOND_IN_MILLIS;

    private HandlerThread mHandlerThread;
    private final Map<BluetoothDevice, BluetoothInfo> mBluetoothCache = Collections.synchronizedMap(new ArrayMap<>());
    private OnBluetoothChangListener mListener;
    private boolean mScannerEnable = false;
    private long starScanMillis = 0;

    private final Comparator<BluetoothInfo> mComparator = new Comparator<BluetoothInfo>() {
        @Override
        public int compare(BluetoothInfo o1, BluetoothInfo o2) {
            if (o1.isBounded() && !o2.isBounded()) {
                return -1;
            }
            if (!o1.isBounded() && o2.isBounded()) {
                return 1;
            }
            return o1.name.compareTo(o2.name);
        }
    };

    // Create a BroadcastReceiver for ACTION_FOUND.
    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            switch (action) {
                case BluetoothDevice.ACTION_ACL_CONNECTED:
                    changeDeviceConnectState(device, true);
                    break;
                case BluetoothDevice.ACTION_ACL_DISCONNECTED:
                    changeDeviceConnectState(device, false);
                    break;
                case BluetoothDevice.ACTION_NAME_CHANGED:
                    String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
                    logI("action:" + action);
                    logI("------->name:" + name);
                    updateDevice(device);
                    notifyBluetoothListChanged();
                    break;
                case BluetoothDevice.ACTION_FOUND:
                    short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, (short) 0);
                    updateDevice(device, rssi);
                    logDevice(device, "found device");
//                    logI("found device:-------------->");
//                    logI(""+device.toString());
//                    logI("name:"+device.getName());
//                    logI("alias:"+device.getAlias());
                    break;
                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                    int boundState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
                    logDevice(device, "bound state changed");
                    logI("bound state changed:" + boundState);

                    updateDevice(device);
                    notifyBluetoothListChanged();
                    break;
                case BluetoothAdapter.ACTION_STATE_CHANGED:
                    int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, STATE_OFF);
                    if (state == STATE_OFF) {
                        pauseScan();
                    } else if (state == STATE_ON) {
                        resumeScan();
                    }
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                    starScanMillis = System.currentTimeMillis();
                    logI("--------discovery started");
                    ThreadUtils.postOnMainThread(() -> mListener.onDiscoveryStart());
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                    long duration = System.currentTimeMillis() - starScanMillis;
                    logI("duration:" + DateUtils.formatElapsedTime(duration / 1000));
                    logI("--------discovery finished");
                    notifyBluetoothListChanged();
                    ThreadUtils.postOnMainThread(() -> mListener.onDiscoveryFinished());
                    sendMsgDiscoveryDelay();
                    break;
            }
        }
    };

    private void changeDeviceConnectState(BluetoothDevice device, boolean b) {
        if (device == null) {
            return;
        }
        BluetoothInfo bluetoothInfo = mBluetoothCache.get(device);
        if (bluetoothInfo == null) {
            return;
        }
        bluetoothInfo.isConn = b;
        notifyBluetoothListChanged();
    }

    private void sendMsgDiscoveryDelay() {
        mHandler.sendEmptyMessageDelayed(MSG_SCAN_BLUETOOTH, DELAY_SCAN_BLUETOOTH);
    }


    public BluetoothScanner(Context context, OnBluetoothChangListener listener) {
        mContext = context;
        mListener = listener;
        HandlerThread handlerThread = new HandlerThread("bluetooth-scanner@" + this.hashCode());
        handlerThread.start();
        setHandlerThread(handlerThread);
        mBluetoothManager = ContextCompat.getSystemService(context, BluetoothManager.class);
        if (mBluetoothManager != null) {
            mAdapter = mBluetoothManager.getAdapter();
            mBluetoothLeScanner = mAdapter.getBluetoothLeScanner();
        }
    }

    @SuppressLint("MissingPermission")
    private void initBoundDevices() {
        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
        for (BluetoothDevice bondedDevice : bondedDevices) {
            updateDevice(bondedDevice);
        }
        notifyBluetoothListChanged();
    }


    @SuppressLint("MissingPermission")
    private void notifyBluetoothListChanged() {
        if (mListener != null) {
            //去除过期的蓝牙设备
            long currentTimeMillis = System.currentTimeMillis();
            ArrayList<BluetoothInfo> bluetoothInfos = new ArrayList<>();
            synchronized (mBluetoothCache) {
                for (BluetoothDevice bluetoothDevice : mBluetoothCache.keySet()) {
                    if (bluetoothDevice == null || bluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
                        continue;
                    }
                    BluetoothInfo bluetoothInfo = mBluetoothCache.get(bluetoothDevice);
                    if (bluetoothInfo == null) {
                        continue;
                    }
                    if (currentTimeMillis - bluetoothInfo.timestamp > EXPIRE_DURATION) {
                        mBluetoothCache.remove(bluetoothDevice);
                    }
                }

                Collection<BluetoothInfo> values = mBluetoothCache.values();
                bluetoothInfos.addAll(values);
                Collections.sort(bluetoothInfos,mComparator);
            }
            ThreadUtils.postOnMainThread(() -> mListener.onBluetoothListChanged(bluetoothInfos));
        }
    }


    @SuppressLint("MissingPermission")
    private BluetoothInfo updateDevice(BluetoothDevice device) {
        if (device == null) {
            return null;
        }
        String deviceName = device.getName();
        if (TextUtils.isEmpty(deviceName)) {
            return null;
        }
        BluetoothInfo bluetoothInfo = mBluetoothCache.get(device);
        if (bluetoothInfo == null) {
            bluetoothInfo = new BluetoothInfo();
            mBluetoothCache.put(device, bluetoothInfo);
        }
        bluetoothInfo.name = deviceName;
        bluetoothInfo.boundState = device.getBondState();
        bluetoothInfo.type = device.getType();
        bluetoothInfo.timestamp = System.currentTimeMillis();
        bluetoothInfo.address = device.getAddress();
        bluetoothInfo.isConn = mBluetoothManager.getConnectionState(device, BluetoothProfile.GATT_SERVER)
                == BluetoothProfile.STATE_CONNECTED;
        return bluetoothInfo;
    }

    private void updateDevice(BluetoothDevice device, int rssi) {
        BluetoothInfo bluetoothInfo = updateDevice(device);
        if (bluetoothInfo != null) {
            bluetoothInfo.rssi = rssi;
        }
    }

    private void setHandlerThread(HandlerThread handlerThread) {
        mHandlerThread = handlerThread;
        mHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                if (msg.what == MSG_SCAN_BLUETOOTH) {
                    startScan();
                }
            }
        };
    }

    @SuppressLint("MissingPermission")
    private void startScan() {
        if (!mAdapter.isDiscovering()) {
            mAdapter.startDiscovery();
        } else {
            mHandler.removeMessages(MSG_SCAN_BLUETOOTH);
        }
    }

    private void runOnHandlerThread(Runnable runnable) {
        if (runnable != null) {
            mHandler.post(runnable);
        }
    }

    private void logE(String msg) {
        if (loggable) {
            Log.e(TAG, "" + msg);
        }
    }

    private void logI(String msg) {
        if (loggable) {
            Log.i(TAG, "" + msg);
        }
    }


    @SuppressLint("MissingPermission")
    private void logDevice(BluetoothDevice device, String prefix) {
        if (device.getName() != null) {
            logI("----->" + prefix + ":" + device.getName()
                    + ",address:" + device.getAddress()
                    + ",rssi:" + device
                    + ",type:" + device.getType()
                    + ",boundstate:" + device.getBondState());
        }
    }

    private final ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);

            BluetoothDevice device = result.getDevice();
//            logDevice(device, "scan result");
//            String type = "";
//            if (callbackType== ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
//                type = "lost";
//            }else if (callbackType==ScanSettings.CALLBACK_TYPE_FIRST_MATCH){
//                type = "first_match";
//            }else {
//                type = "all_match";
//            }
//            BluetoothDevice device = result.getDevice();
//            logI("scan result type:"+type);
//            logI("scan result device:"+ device);
//            updateDevice(device,result.getRssi());
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
            if (results == null) {
                return;
            }
            runOnHandlerThread(() -> {
                for (ScanResult result : results) {
                    updateDevice(result.getDevice(), result.getRssi());
                }
            });
            notifyBluetoothListChanged();
            if (loggable) {
                List<BluetoothDevice> list = new ArrayList<>();
                for (ScanResult result : results) {
                    list.add(result.getDevice());
                }
                logI("batch scan result:" + list);
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

    @SuppressLint("MissingPermission")
    private void resumeScan() {
        logI("resumeScan");
        if (mAdapter.isEnabled()) {
            mHandler.removeMessages(MSG_SCAN_BLUETOOTH);
            mHandler.sendEmptyMessage(MSG_SCAN_BLUETOOTH);
            if (mScannerEnable) {
                if (mBluetoothLeScanner == null) {
                    mBluetoothLeScanner = mAdapter.getBluetoothLeScanner();
                }
                mBluetoothLeScanner.startScan(mScanCallback);
            }
        } else {
            logE("bluetooth switch is off");
        }

    }

    @SuppressLint("MissingPermission")
    private void pauseScan() {
        logI("pauseScan");
        if (mHandler.hasMessages(MSG_SCAN_BLUETOOTH)) {
            mHandler.removeMessages(MSG_SCAN_BLUETOOTH);
        }
        mAdapter.cancelDiscovery();
        if (mAdapter.isEnabled()) {
            if (mScannerEnable && mBluetoothLeScanner != null) {
                mBluetoothLeScanner.stopScan(mScanCallback);
            }
        }
    }


    @Override
    public void register() {
        // Register for broadcasts when a device is discovered.
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);

        //本地蓝牙连接状态改变
        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
        //蓝牙开关状态改变
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        mContext.registerReceiver(receiver, filter, null, mHandler);
        resumeScan();
        initBoundDevices();
    }

    @Override
    public void unregister() {
        pauseScan();
        if (mHandlerThread != null) {
            mHandlerThread.quit();
        }
        mContext.unregisterReceiver(receiver);
    }
}
